/*
 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing.plaf.basic;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.table.*;

import sun.swing.*;

/**
 * BasicTableHeaderUI implementation
 *
 * @author Alan Chung
 * @author Philip Milne
 */
public class BasicTableHeaderUI extends TableHeaderUI {

  private static Cursor resizeCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);

//
// Instance Variables
//

  /**
   * The JTableHeader that is delegating the painting to this UI.
   */
  protected JTableHeader header;
  protected CellRendererPane rendererPane;

  // Listeners that are attached to the JTable
  protected MouseInputListener mouseInputListener;

  // The column header over which the mouse currently is.
  private int rolloverColumn = -1;

  // The column that should be highlighted when the table header has the focus.
  private int selectedColumnIndex = 0; // Read ONLY via getSelectedColumnIndex!

  private static FocusListener focusListener = new FocusListener() {
    public void focusGained(FocusEvent e) {
      repaintHeader(e.getSource());
    }

    public void focusLost(FocusEvent e) {
      repaintHeader(e.getSource());
    }

    private void repaintHeader(Object source) {
      if (source instanceof JTableHeader) {
        JTableHeader th = (JTableHeader) source;
        BasicTableHeaderUI ui =
            (BasicTableHeaderUI) BasicLookAndFeel.
                getUIOfType(th.getUI(),
                    BasicTableHeaderUI.class);
        if (ui == null) {
          return;
        }

        th.repaint(th.getHeaderRect(ui.getSelectedColumnIndex()));
      }
    }
  };

  /**
   * This class should be treated as a &quot;protected&quot; inner class.
   * Instantiate it only within subclasses of {@code BasicTableHeaderUI}.
   */
  public class MouseInputHandler implements MouseInputListener {

    private int mouseXOffset;
    private Cursor otherCursor = resizeCursor;

    public void mouseClicked(MouseEvent e) {
      if (!header.isEnabled()) {
        return;
      }
      if (e.getClickCount() % 2 == 1 &&
          SwingUtilities.isLeftMouseButton(e)) {
        JTable table = header.getTable();
        RowSorter sorter;
        if (table != null && (sorter = table.getRowSorter()) != null) {
          int columnIndex = header.columnAtPoint(e.getPoint());
          if (columnIndex != -1) {
            columnIndex = table.convertColumnIndexToModel(
                columnIndex);
            sorter.toggleSortOrder(columnIndex);
          }
        }
      }
    }

    private TableColumn getResizingColumn(Point p) {
      return getResizingColumn(p, header.columnAtPoint(p));
    }

    private TableColumn getResizingColumn(Point p, int column) {
      if (column == -1) {
        return null;
      }
      Rectangle r = header.getHeaderRect(column);
      r.grow(-3, 0);
      if (r.contains(p)) {
        return null;
      }
      int midPoint = r.x + r.width / 2;
      int columnIndex;
      if (header.getComponentOrientation().isLeftToRight()) {
        columnIndex = (p.x < midPoint) ? column - 1 : column;
      } else {
        columnIndex = (p.x < midPoint) ? column : column - 1;
      }
      if (columnIndex == -1) {
        return null;
      }
      return header.getColumnModel().getColumn(columnIndex);
    }

    public void mousePressed(MouseEvent e) {
      if (!header.isEnabled()) {
        return;
      }
      header.setDraggedColumn(null);
      header.setResizingColumn(null);
      header.setDraggedDistance(0);

      Point p = e.getPoint();

      // First find which header cell was hit
      TableColumnModel columnModel = header.getColumnModel();
      int index = header.columnAtPoint(p);

      if (index != -1) {
        // The last 3 pixels + 3 pixels of next column are for resizing
        TableColumn resizingColumn = getResizingColumn(p, index);
        if (canResize(resizingColumn, header)) {
          header.setResizingColumn(resizingColumn);
          if (header.getComponentOrientation().isLeftToRight()) {
            mouseXOffset = p.x - resizingColumn.getWidth();
          } else {
            mouseXOffset = p.x + resizingColumn.getWidth();
          }
        } else if (header.getReorderingAllowed()) {
          TableColumn hitColumn = columnModel.getColumn(index);
          header.setDraggedColumn(hitColumn);
          mouseXOffset = p.x;
        }
      }

      if (header.getReorderingAllowed()) {
        int oldRolloverColumn = rolloverColumn;
        rolloverColumn = -1;
        rolloverColumnUpdated(oldRolloverColumn, rolloverColumn);
      }
    }

    private void swapCursor() {
      Cursor tmp = header.getCursor();
      header.setCursor(otherCursor);
      otherCursor = tmp;
    }

    public void mouseMoved(MouseEvent e) {
      if (!header.isEnabled()) {
        return;
      }
      if (canResize(getResizingColumn(e.getPoint()), header) !=
          (header.getCursor() == resizeCursor)) {
        swapCursor();
      }
      updateRolloverColumn(e);
    }

    public void mouseDragged(MouseEvent e) {
      if (!header.isEnabled()) {
        return;
      }
      int mouseX = e.getX();

      TableColumn resizingColumn = header.getResizingColumn();
      TableColumn draggedColumn = header.getDraggedColumn();

      boolean headerLeftToRight = header.getComponentOrientation().isLeftToRight();

      if (resizingColumn != null) {
        int oldWidth = resizingColumn.getWidth();
        int newWidth;
        if (headerLeftToRight) {
          newWidth = mouseX - mouseXOffset;
        } else {
          newWidth = mouseXOffset - mouseX;
        }
        mouseXOffset += changeColumnWidth(resizingColumn, header,
            oldWidth, newWidth);
      } else if (draggedColumn != null) {
        TableColumnModel cm = header.getColumnModel();
        int draggedDistance = mouseX - mouseXOffset;
        int direction = (draggedDistance < 0) ? -1 : 1;
        int columnIndex = viewIndexForColumn(draggedColumn);
        int newColumnIndex = columnIndex + (headerLeftToRight ? direction : -direction);
        if (0 <= newColumnIndex && newColumnIndex < cm.getColumnCount()) {
          int width = cm.getColumn(newColumnIndex).getWidth();
          if (Math.abs(draggedDistance) > (width / 2)) {

            mouseXOffset = mouseXOffset + direction * width;
            header.setDraggedDistance(draggedDistance - direction * width);

            //Cache the selected column.
            int selectedIndex =
                SwingUtilities2.convertColumnIndexToModel(
                    header.getColumnModel(),
                    getSelectedColumnIndex());

            //Now do the move.
            cm.moveColumn(columnIndex, newColumnIndex);

            //Update the selected index.
            selectColumn(
                SwingUtilities2.convertColumnIndexToView(
                    header.getColumnModel(), selectedIndex),
                false);

            return;
          }
        }
        setDraggedDistance(draggedDistance, columnIndex);
      }

      updateRolloverColumn(e);
    }

    public void mouseReleased(MouseEvent e) {
      if (!header.isEnabled()) {
        return;
      }
      setDraggedDistance(0, viewIndexForColumn(header.getDraggedColumn()));

      header.setResizingColumn(null);
      header.setDraggedColumn(null);

      updateRolloverColumn(e);
    }

    public void mouseEntered(MouseEvent e) {
      if (!header.isEnabled()) {
        return;
      }
      updateRolloverColumn(e);
    }

    public void mouseExited(MouseEvent e) {
      if (!header.isEnabled()) {
        return;
      }
      int oldRolloverColumn = rolloverColumn;
      rolloverColumn = -1;
      rolloverColumnUpdated(oldRolloverColumn, rolloverColumn);
    }
//
// Protected & Private Methods
//

    private void setDraggedDistance(int draggedDistance, int column) {
      header.setDraggedDistance(draggedDistance);
      if (column != -1) {
        header.getColumnModel().moveColumn(column, column);
      }
    }
  }

//
//  Factory methods for the Listeners
//

  /**
   * Creates the mouse listener for the JTableHeader.
   */
  protected MouseInputListener createMouseInputListener() {
    return new MouseInputHandler();
  }

//
//  The installation/uninstall procedures and support
//

  public static ComponentUI createUI(JComponent h) {
    return new BasicTableHeaderUI();
  }

//  Installation

  public void installUI(JComponent c) {
    header = (JTableHeader) c;

    rendererPane = new CellRendererPane();
    header.add(rendererPane);

    installDefaults();
    installListeners();
    installKeyboardActions();
  }

  /**
   * Initializes JTableHeader properties such as font, foreground, and background.
   * The font, foreground, and background properties are only set if their
   * current value is either null or a UIResource, other properties are set
   * if the current value is null.
   *
   * @see #installUI
   */
  protected void installDefaults() {
    LookAndFeel.installColorsAndFont(header, "TableHeader.background",
        "TableHeader.foreground", "TableHeader.font");
    LookAndFeel.installProperty(header, "opaque", Boolean.TRUE);
  }

  /**
   * Attaches listeners to the JTableHeader.
   */
  protected void installListeners() {
    mouseInputListener = createMouseInputListener();

    header.addMouseListener(mouseInputListener);
    header.addMouseMotionListener(mouseInputListener);
    header.addFocusListener(focusListener);
  }

  /**
   * Register all keyboard actions on the JTableHeader.
   */
  protected void installKeyboardActions() {
    InputMap keyMap = (InputMap) DefaultLookup.get(header, this,
        "TableHeader.ancestorInputMap");
    SwingUtilities.replaceUIInputMap(header,
        JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keyMap);
    LazyActionMap.installLazyActionMap(header, BasicTableHeaderUI.class,
        "TableHeader.actionMap");
  }

// Uninstall methods

  public void uninstallUI(JComponent c) {
    uninstallDefaults();
    uninstallListeners();
    uninstallKeyboardActions();

    header.remove(rendererPane);
    rendererPane = null;
    header = null;
  }

  protected void uninstallDefaults() {
  }

  protected void uninstallListeners() {
    header.removeMouseListener(mouseInputListener);
    header.removeMouseMotionListener(mouseInputListener);

    mouseInputListener = null;
  }

  /**
   * Unregisters default key actions.
   */
  protected void uninstallKeyboardActions() {
    SwingUtilities.replaceUIInputMap(header, JComponent.WHEN_FOCUSED, null);
    SwingUtilities.replaceUIActionMap(header, null);
  }

  /**
   * Populates TableHeader's actions.
   */
  static void loadActionMap(LazyActionMap map) {
    map.put(new Actions(Actions.TOGGLE_SORT_ORDER));
    map.put(new Actions(Actions.SELECT_COLUMN_TO_LEFT));
    map.put(new Actions(Actions.SELECT_COLUMN_TO_RIGHT));
    map.put(new Actions(Actions.MOVE_COLUMN_LEFT));
    map.put(new Actions(Actions.MOVE_COLUMN_RIGHT));
    map.put(new Actions(Actions.RESIZE_LEFT));
    map.put(new Actions(Actions.RESIZE_RIGHT));
    map.put(new Actions(Actions.FOCUS_TABLE));
  }

//
// Support for mouse rollover
//

  /**
   * Returns the index of the column header over which the mouse
   * currently is. When the mouse is not over the table header,
   * -1 is returned.
   *
   * @return the index of the current rollover column
   * @see #rolloverColumnUpdated(int, int)
   * @since 1.6
   */
  protected int getRolloverColumn() {
    return rolloverColumn;
  }

  /**
   * This method gets called every time when a rollover column in the table
   * header is updated. Every look and feel that supports a rollover effect
   * in a table header should override this method and repaint the header.
   *
   * @param oldColumn the index of the previous rollover column or -1 if the mouse was not over a
   * column
   * @param newColumn the index of the new rollover column or -1 if the mouse is not over a column
   * @see #getRolloverColumn()
   * @see JTableHeader#getHeaderRect(int)
   * @since 1.6
   */
  protected void rolloverColumnUpdated(int oldColumn, int newColumn) {
  }

  private void updateRolloverColumn(MouseEvent e) {
    if (header.getDraggedColumn() == null &&
        header.contains(e.getPoint())) {

      int col = header.columnAtPoint(e.getPoint());
      if (col != rolloverColumn) {
        int oldRolloverColumn = rolloverColumn;
        rolloverColumn = col;
        rolloverColumnUpdated(oldRolloverColumn, rolloverColumn);
      }
    }
  }

  //
// Support for keyboard and mouse access
//
  private int selectNextColumn(boolean doIt) {
    int newIndex = getSelectedColumnIndex();
    if (newIndex < header.getColumnModel().getColumnCount() - 1) {
      newIndex++;
      if (doIt) {
        selectColumn(newIndex);
      }
    }
    return newIndex;
  }

  private int selectPreviousColumn(boolean doIt) {
    int newIndex = getSelectedColumnIndex();
    if (newIndex > 0) {
      newIndex--;
      if (doIt) {
        selectColumn(newIndex);
      }
    }
    return newIndex;
  }

  /**
   * Selects the specified column in the table header. Repaints the
   * affected header cells and makes sure the newly selected one is visible.
   */
  void selectColumn(int newColIndex) {
    selectColumn(newColIndex, true);
  }

  void selectColumn(int newColIndex, boolean doScroll) {
    Rectangle repaintRect = header.getHeaderRect(selectedColumnIndex);
    header.repaint(repaintRect);
    selectedColumnIndex = newColIndex;
    repaintRect = header.getHeaderRect(newColIndex);
    header.repaint(repaintRect);
    if (doScroll) {
      scrollToColumn(newColIndex);
    }
    return;
  }

  /**
   * Used by selectColumn to scroll horizontally, if necessary,
   * to ensure that the newly selected column is visible.
   */
  private void scrollToColumn(int col) {
    Container container;
    JTable table;

    //Test whether the header is in a scroll pane and has a table.
    if ((header.getParent() == null) ||
        ((container = header.getParent().getParent()) == null) ||
        !(container instanceof JScrollPane) ||
        ((table = header.getTable()) == null)) {
      return;
    }

    //Now scroll, if necessary.
    Rectangle vis = table.getVisibleRect();
    Rectangle cellBounds = table.getCellRect(0, col, true);
    vis.x = cellBounds.x;
    vis.width = cellBounds.width;
    table.scrollRectToVisible(vis);
  }

  private int getSelectedColumnIndex() {
    int numCols = header.getColumnModel().getColumnCount();
    if (selectedColumnIndex >= numCols && numCols > 0) {
      selectedColumnIndex = numCols - 1;
    }
    return selectedColumnIndex;
  }

  private static boolean canResize(TableColumn column,
      JTableHeader header) {
    return (column != null) && header.getResizingAllowed()
        && column.getResizable();
  }

  private int changeColumnWidth(TableColumn resizingColumn,
      JTableHeader th,
      int oldWidth, int newWidth) {
    resizingColumn.setWidth(newWidth);

    Container container;
    JTable table;

    if ((th.getParent() == null) ||
        ((container = th.getParent().getParent()) == null) ||
        !(container instanceof JScrollPane) ||
        ((table = th.getTable()) == null)) {
      return 0;
    }

    if (!container.getComponentOrientation().isLeftToRight() &&
        !th.getComponentOrientation().isLeftToRight()) {
      JViewport viewport = ((JScrollPane) container).getViewport();
      int viewportWidth = viewport.getWidth();
      int diff = newWidth - oldWidth;
      int newHeaderWidth = table.getWidth() + diff;

                /* Resize a table */
      Dimension tableSize = table.getSize();
      tableSize.width += diff;
      table.setSize(tableSize);

                /* If this table is in AUTO_RESIZE_OFF mode and
                 * has a horizontal scrollbar, we need to update
                 * a view's position.
                 */
      if ((newHeaderWidth >= viewportWidth) &&
          (table.getAutoResizeMode() == JTable.AUTO_RESIZE_OFF)) {
        Point p = viewport.getViewPosition();
        p.x = Math.max(0, Math.min(newHeaderWidth - viewportWidth,
            p.x + diff));
        viewport.setViewPosition(p);
        return diff;
      }
    }
    return 0;
  }

//
// Baseline
//

  /**
   * Returns the baseline.
   *
   * @throws NullPointerException {@inheritDoc}
   * @throws IllegalArgumentException {@inheritDoc}
   * @see javax.swing.JComponent#getBaseline(int, int)
   * @since 1.6
   */
  public int getBaseline(JComponent c, int width, int height) {
    super.getBaseline(c, width, height);
    int baseline = -1;
    TableColumnModel columnModel = header.getColumnModel();
    for (int column = 0; column < columnModel.getColumnCount();
        column++) {
      TableColumn aColumn = columnModel.getColumn(column);
      Component comp = getHeaderRenderer(column);
      Dimension pref = comp.getPreferredSize();
      int columnBaseline = comp.getBaseline(pref.width, height);
      if (columnBaseline >= 0) {
        if (baseline == -1) {
          baseline = columnBaseline;
        } else if (baseline != columnBaseline) {
          baseline = -1;
          break;
        }
      }
    }
    return baseline;
  }

//
// Paint Methods and support
//

  public void paint(Graphics g, JComponent c) {
    if (header.getColumnModel().getColumnCount() <= 0) {
      return;
    }
    boolean ltr = header.getComponentOrientation().isLeftToRight();

    Rectangle clip = g.getClipBounds();
    Point left = clip.getLocation();
    Point right = new Point(clip.x + clip.width - 1, clip.y);
    TableColumnModel cm = header.getColumnModel();
    int cMin = header.columnAtPoint(ltr ? left : right);
    int cMax = header.columnAtPoint(ltr ? right : left);
    // This should never happen.
    if (cMin == -1) {
      cMin = 0;
    }
    // If the table does not have enough columns to fill the view we'll get -1.
    // Replace this with the index of the last column.
    if (cMax == -1) {
      cMax = cm.getColumnCount() - 1;
    }

    TableColumn draggedColumn = header.getDraggedColumn();
    int columnWidth;
    Rectangle cellRect = header.getHeaderRect(ltr ? cMin : cMax);
    TableColumn aColumn;
    if (ltr) {
      for (int column = cMin; column <= cMax; column++) {
        aColumn = cm.getColumn(column);
        columnWidth = aColumn.getWidth();
        cellRect.width = columnWidth;
        if (aColumn != draggedColumn) {
          paintCell(g, cellRect, column);
        }
        cellRect.x += columnWidth;
      }
    } else {
      for (int column = cMax; column >= cMin; column--) {
        aColumn = cm.getColumn(column);
        columnWidth = aColumn.getWidth();
        cellRect.width = columnWidth;
        if (aColumn != draggedColumn) {
          paintCell(g, cellRect, column);
        }
        cellRect.x += columnWidth;
      }
    }

    // Paint the dragged column if we are dragging.
    if (draggedColumn != null) {
      int draggedColumnIndex = viewIndexForColumn(draggedColumn);
      Rectangle draggedCellRect = header.getHeaderRect(draggedColumnIndex);

      // Draw a gray well in place of the moving column.
      g.setColor(header.getParent().getBackground());
      g.fillRect(draggedCellRect.x, draggedCellRect.y,
          draggedCellRect.width, draggedCellRect.height);

      draggedCellRect.x += header.getDraggedDistance();

      // Fill the background.
      g.setColor(header.getBackground());
      g.fillRect(draggedCellRect.x, draggedCellRect.y,
          draggedCellRect.width, draggedCellRect.height);

      paintCell(g, draggedCellRect, draggedColumnIndex);
    }

    // Remove all components in the rendererPane.
    rendererPane.removeAll();
  }

  private Component getHeaderRenderer(int columnIndex) {
    TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
    TableCellRenderer renderer = aColumn.getHeaderRenderer();
    if (renderer == null) {
      renderer = header.getDefaultRenderer();
    }

    boolean hasFocus = !header.isPaintingForPrint()
        && (columnIndex == getSelectedColumnIndex())
        && header.hasFocus();
    return renderer.getTableCellRendererComponent(header.getTable(),
        aColumn.getHeaderValue(),
        false, hasFocus,
        -1, columnIndex);
  }

  private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
    Component component = getHeaderRenderer(columnIndex);
    rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y,
        cellRect.width, cellRect.height, true);
  }

  private int viewIndexForColumn(TableColumn aColumn) {
    TableColumnModel cm = header.getColumnModel();
    for (int column = 0; column < cm.getColumnCount(); column++) {
      if (cm.getColumn(column) == aColumn) {
        return column;
      }
    }
    return -1;
  }

//
// Size Methods
//

  private int getHeaderHeight() {
    int height = 0;
    boolean accomodatedDefault = false;
    TableColumnModel columnModel = header.getColumnModel();
    for (int column = 0; column < columnModel.getColumnCount(); column++) {
      TableColumn aColumn = columnModel.getColumn(column);
      boolean isDefault = (aColumn.getHeaderRenderer() == null);

      if (!isDefault || !accomodatedDefault) {
        Component comp = getHeaderRenderer(column);
        int rendererHeight = comp.getPreferredSize().height;
        height = Math.max(height, rendererHeight);

        // Configuring the header renderer to calculate its preferred size
        // is expensive. Optimise this by assuming the default renderer
        // always has the same height as the first non-zero height that
        // it returns for a non-null/non-empty value.
        if (isDefault && rendererHeight > 0) {
          Object headerValue = aColumn.getHeaderValue();
          if (headerValue != null) {
            headerValue = headerValue.toString();

            if (headerValue != null && !headerValue.equals("")) {
              accomodatedDefault = true;
            }
          }
        }
      }
    }
    return height;
  }

  private Dimension createHeaderSize(long width) {
    // None of the callers include the intercell spacing, do it here.
    if (width > Integer.MAX_VALUE) {
      width = Integer.MAX_VALUE;
    }
    return new Dimension((int) width, getHeaderHeight());
  }


  /**
   * Return the minimum size of the header. The minimum width is the sum
   * of the minimum widths of each column (plus inter-cell spacing).
   */
  public Dimension getMinimumSize(JComponent c) {
    long width = 0;
    Enumeration enumeration = header.getColumnModel().getColumns();
    while (enumeration.hasMoreElements()) {
      TableColumn aColumn = (TableColumn) enumeration.nextElement();
      width = width + aColumn.getMinWidth();
    }
    return createHeaderSize(width);
  }

  /**
   * Return the preferred size of the header. The preferred height is the
   * maximum of the preferred heights of all of the components provided
   * by the header renderers. The preferred width is the sum of the
   * preferred widths of each column (plus inter-cell spacing).
   */
  public Dimension getPreferredSize(JComponent c) {
    long width = 0;
    Enumeration enumeration = header.getColumnModel().getColumns();
    while (enumeration.hasMoreElements()) {
      TableColumn aColumn = (TableColumn) enumeration.nextElement();
      width = width + aColumn.getPreferredWidth();
    }
    return createHeaderSize(width);
  }

  /**
   * Return the maximum size of the header. The maximum width is the sum
   * of the maximum widths of each column (plus inter-cell spacing).
   */
  public Dimension getMaximumSize(JComponent c) {
    long width = 0;
    Enumeration enumeration = header.getColumnModel().getColumns();
    while (enumeration.hasMoreElements()) {
      TableColumn aColumn = (TableColumn) enumeration.nextElement();
      width = width + aColumn.getMaxWidth();
    }
    return createHeaderSize(width);
  }

  private static class Actions extends UIAction {

    public static final String TOGGLE_SORT_ORDER =
        "toggleSortOrder";
    public static final String SELECT_COLUMN_TO_LEFT =
        "selectColumnToLeft";
    public static final String SELECT_COLUMN_TO_RIGHT =
        "selectColumnToRight";
    public static final String MOVE_COLUMN_LEFT =
        "moveColumnLeft";
    public static final String MOVE_COLUMN_RIGHT =
        "moveColumnRight";
    public static final String RESIZE_LEFT =
        "resizeLeft";
    public static final String RESIZE_RIGHT =
        "resizeRight";
    public static final String FOCUS_TABLE =
        "focusTable";

    public Actions(String name) {
      super(name);
    }

    public boolean isEnabled(Object sender) {
      if (sender instanceof JTableHeader) {
        JTableHeader th = (JTableHeader) sender;
        TableColumnModel cm = th.getColumnModel();
        if (cm.getColumnCount() <= 0) {
          return false;
        }

        String key = getName();
        BasicTableHeaderUI ui =
            (BasicTableHeaderUI) BasicLookAndFeel.getUIOfType(th.getUI(),
                BasicTableHeaderUI.class);
        if (ui != null) {
          if (key == MOVE_COLUMN_LEFT) {
            return th.getReorderingAllowed()
                && maybeMoveColumn(true, th, ui, false);
          } else if (key == MOVE_COLUMN_RIGHT) {
            return th.getReorderingAllowed()
                && maybeMoveColumn(false, th, ui, false);
          } else if (key == RESIZE_LEFT ||
              key == RESIZE_RIGHT) {
            return canResize(cm.getColumn(ui.getSelectedColumnIndex()), th);
          } else if (key == FOCUS_TABLE) {
            return (th.getTable() != null);
          }
        }
      }
      return true;
    }

    public void actionPerformed(ActionEvent e) {
      JTableHeader th = (JTableHeader) e.getSource();
      BasicTableHeaderUI ui =
          (BasicTableHeaderUI) BasicLookAndFeel.
              getUIOfType(th.getUI(),
                  BasicTableHeaderUI.class);
      if (ui == null) {
        return;
      }

      String name = getName();
      if (TOGGLE_SORT_ORDER == name) {
        JTable table = th.getTable();
        RowSorter sorter = table == null ? null : table.getRowSorter();
        if (sorter != null) {
          int columnIndex = ui.getSelectedColumnIndex();
          columnIndex = table.convertColumnIndexToModel(
              columnIndex);
          sorter.toggleSortOrder(columnIndex);
        }
      } else if (SELECT_COLUMN_TO_LEFT == name) {
        if (th.getComponentOrientation().isLeftToRight()) {
          ui.selectPreviousColumn(true);
        } else {
          ui.selectNextColumn(true);
        }
      } else if (SELECT_COLUMN_TO_RIGHT == name) {
        if (th.getComponentOrientation().isLeftToRight()) {
          ui.selectNextColumn(true);
        } else {
          ui.selectPreviousColumn(true);
        }
      } else if (MOVE_COLUMN_LEFT == name) {
        moveColumn(true, th, ui);
      } else if (MOVE_COLUMN_RIGHT == name) {
        moveColumn(false, th, ui);
      } else if (RESIZE_LEFT == name) {
        resize(true, th, ui);
      } else if (RESIZE_RIGHT == name) {
        resize(false, th, ui);
      } else if (FOCUS_TABLE == name) {
        JTable table = th.getTable();
        if (table != null) {
          table.requestFocusInWindow();
        }
      }
    }

    private void moveColumn(boolean leftArrow, JTableHeader th,
        BasicTableHeaderUI ui) {
      maybeMoveColumn(leftArrow, th, ui, true);
    }

    private boolean maybeMoveColumn(boolean leftArrow, JTableHeader th,
        BasicTableHeaderUI ui, boolean doIt) {
      int oldIndex = ui.getSelectedColumnIndex();
      int newIndex;

      if (th.getComponentOrientation().isLeftToRight()) {
        newIndex = leftArrow ? ui.selectPreviousColumn(doIt)
            : ui.selectNextColumn(doIt);
      } else {
        newIndex = leftArrow ? ui.selectNextColumn(doIt)
            : ui.selectPreviousColumn(doIt);
      }

      if (newIndex != oldIndex) {
        if (doIt) {
          th.getColumnModel().moveColumn(oldIndex, newIndex);
        } else {
          return true; // we'd do the move if asked
        }
      }

      return false;
    }

    private void resize(boolean leftArrow, JTableHeader th,
        BasicTableHeaderUI ui) {
      int columnIndex = ui.getSelectedColumnIndex();
      TableColumn resizingColumn =
          th.getColumnModel().getColumn(columnIndex);

      th.setResizingColumn(resizingColumn);
      int oldWidth = resizingColumn.getWidth();
      int newWidth = oldWidth;

      if (th.getComponentOrientation().isLeftToRight()) {
        newWidth = newWidth + (leftArrow ? -1 : 1);
      } else {
        newWidth = newWidth + (leftArrow ? 1 : -1);
      }

      ui.changeColumnWidth(resizingColumn, th, oldWidth, newWidth);
    }
  }
}  // End of Class BasicTableHeaderUI
